package com.liuxinlong.modules.quartz.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.liuxinlong.common.AosException;
import com.liuxinlong.common.SnowFlake;
import com.liuxinlong.enums.ReturnCodes;
import com.liuxinlong.enums.TaskStatusEnum;
import com.liuxinlong.jobs.QuartzJobFactory;
import com.liuxinlong.modules.dao.QuartzTaskDao;
import com.liuxinlong.modules.entity.QuartzTask;
import com.liuxinlong.modules.quartz.service.QuartzService;
import com.liuxinlong.utils.ObjectUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 定时任务服务层实现
 *
 * @author liuxinlong@hotwater.com.cn
 * @version 1.0.0
 * @since 2022年6月28日
 */
@Slf4j
@SuppressWarnings("unchecked")
@Service
public class QuartzServiceImpl implements QuartzService {
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @Autowired
    private QuartzTaskDao quartzTaskDao;

    @Override
    public List<Map<String, Object>> pageQuartzJobs(Map<String, Object> queryParam) {
        int startNum = (int) queryParam.get("startNum");
        List<QuartzTask> taskList = quartzTaskDao.pageTaskList(queryParam);
        return completeResult(taskList, startNum);
    }

    @Override
    public int getQuartzJobsCount(Map<String, Object> queryParam) {
        return quartzTaskDao.getQuartzJobsCount(queryParam);
    }

    @Override
    public void addTask(QuartzTask jobInfo) {
        jobInfo.setId(SnowFlake.nextIdStr());
        quartzTaskDao.insert(jobInfo);
    }

    @Override
    public void updateTask(QuartzTask jobInfo) {
        quartzTaskDao.updateById(jobInfo);
    }

    @Override
    public void enableJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        changeJobStatus(id,TaskStatusEnum.READY.getValue());
        runAJobNow(quartzTask);
    }

    @Override
    public void startJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        runAJobNow(quartzTask);
    }

    @Override
    public void pauseJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        pauseJob(quartzTask);
        changeJobStatus(id,TaskStatusEnum.PAUSED.getValue());
    }

    @Override
    public void suspendJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        if (ObjectUtils.isEmpty(quartzTask)) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR,"定时任务挂起失败，定时任务不存在");
        }
        changeJobStatus(id,TaskStatusEnum.SUSPEND.getValue());
    }

    @Override
    public void resumeJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        changeJobStatus(id,TaskStatusEnum.READY.getValue());
        resumeJob(quartzTask);
    }

    @Override
    public void rescheduleJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        if (ObjectUtils.isEmpty(quartzTask)) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR,"定时任务重启失败，定时任务不存在");
        }
        if(quartzTask.getStatus() != TaskStatusEnum.SUSPEND.getValue()) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR,"定时任务重启失败，定时任务未挂起");
        }
        changeJobStatus(id,TaskStatusEnum.READY.getValue());
    }

    @Override
    public void deleteJob(String id) {
        QuartzTask quartzTask = quartzTaskDao.selectById(id);
        if (ObjectUtils.isEmpty(quartzTask)) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR,"定时任务删除失败，定时任务不存在");
        }
        changeJobStatus(id,TaskStatusEnum.SUSPEND.getValue());
        quartzTaskDao.deleteById(id);
    }

    /**
     * 改变定时任务状态
     *
     * @param id     定时任务id
     * @param status 目标状态
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void changeJobStatus(String id, Integer status) {
        QuartzTask job = quartzTaskDao.selectById(id);
        try {
            if (status == 1) {
                // 启动
                job.setStatus(status);
                addJob(job);
            } else if (status == 2) {
                // 暂停
                job.setStatus(status);
            } else if (status == 3) {
                // 挂起
                deleteJob(job);
                job.setStatus(status);
            }
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, e.toString());
        }

        // 更新定时任务
        quartzTaskDao.updateById(job);
    }

    /**
     * 更新定时任务cron表达式与运行状态
     *
     * @param jobId  定时任务主键ID
     * @param cron   表达式
     * @param status 定时状态
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateCronStatus(String jobId, String cron, Integer status) throws SchedulerException {
        QuartzTask job = quartzTaskDao.selectById(jobId);
        if (status == 1) {
            // 启动
            job.setStatus(status);
            job.setCron(cron);
            addJob(job);
        } else if (status == 2) {
            // 停止
            deleteJob(job);
            job.setStatus(status);
            job.setCron(cron);
        }
        // 更新定时任务
        quartzTaskDao.updateById(job);
    }

    /**
     * 添加任务
     *
     * @param job 定时任务
     * @throws SchedulerException
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void addJob(QuartzTask job) throws SchedulerException {
        // 定时任务运行状态为1则进入下一步
        if (job == null || !(TaskStatusEnum.READY.getValue() == job.getStatus())) {
            return;
        }
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        log.info(scheduler + ".......................................................................................add");
        TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

        // 不存在，创建一个
        if (null == trigger) {
//            Class clazz = Constant.CONCURRENT_IS.equals(job.getIsConcurrent()) ? QuartzJobFactory.class : QuartzJobFactoryDisallowConcurrentExecution.class;
            Class clazz = QuartzJobFactory.class;
            JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).build();
            jobDetail.getJobDataMap().put("quartzTask", job);
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCron());
            trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build();
            scheduler.scheduleJob(jobDetail, trigger);
        } else {
            // Trigger已存在，那么更新相应的定时设置
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCron());
            // 按新的cronExpression表达式重新构建trigger
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, trigger);
        }
    }

    @PostConstruct
    public void init() throws Exception {
        log.info("实例化List<quartzTask>,从数据库读取....", this);
        // 这里获取任务信息数据
        List<QuartzTask> jobList = quartzTaskDao.selectList(null);
        for (QuartzTask job : jobList) {
            addJob(job);
        }
    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return 所有计划中的任务列表
     */
    public List<QuartzTask> getAllJob() throws SchedulerException {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            List<QuartzTask> jobList = new ArrayList<QuartzTask>();
            for (JobKey jobKey : jobKeys) {
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    QuartzTask job = new QuartzTask();
                    job.setJobName(jobKey.getName());
                    job.setJobGroup(jobKey.getGroup());
                    job.setRemark("触发器:" + trigger.getKey());
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    job.setStatus(Integer.parseInt(triggerState.name()));
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        job.setCron(cronExpression);
                    }
                    jobList.add(job);
                }
            }
            return jobList;
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "查询所有计划中的任务列表失败");
        }

    }

    /**
     * 查询所有正在运行的job
     *
     * @return 所有正在运行的job列表
     */
    public List<QuartzTask> getRunningJob() {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
            List<QuartzTask> jobList = new ArrayList<QuartzTask>(executingJobs.size());

            for (JobExecutionContext executingJob : executingJobs) {
                QuartzTask job = new QuartzTask();
                JobDetail jobDetail = executingJob.getJobDetail();
                JobKey jobKey = jobDetail.getKey();
                Trigger trigger = executingJob.getTrigger();
                job.setJobName(jobKey.getName());
                job.setJobGroup(jobKey.getGroup());
                job.setRemark("触发器:" + trigger.getKey());
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                job.setStatus(Integer.parseInt(triggerState.name()));
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    job.setCron(cronExpression);
                }
                jobList.add(job);

            }
            return jobList;
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "查询所有正在运行的job失败");
        }

    }

    /**
     * 暂停一个job
     *
     * @param quartzTask 定时任务
     */
    public void pauseJob(QuartzTask quartzTask) {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(quartzTask.getJobName(), quartzTask.getJobGroup());
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "暂停定时任务失败");
        }
    }

    /**
     * 恢复一个job
     *
     * @param quartzTask 定时任务
     */
    public void resumeJob(QuartzTask quartzTask) {

        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(quartzTask.getJobName(), quartzTask.getJobGroup());
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "恢复定时任务失败");
        }
    }

    /**
     * 删除一个job
     *
     * @param quartzTask 定时任务
     */
    public void deleteJob(QuartzTask quartzTask) {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(quartzTask.getJobName(), quartzTask.getJobGroup());
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "删除定时任务失败");
        }
    }

    /**
     * 立即执行job
     *
     * @param quartzTask 定时任务信息
     */
    public void runAJobNow(QuartzTask quartzTask) {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobKey jobKey = JobKey.jobKey(quartzTask.getJobName(), quartzTask.getJobGroup());
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "执行定时任务失败");
        }

    }

    /**
     * 更新job时间表达式
     *
     * @param quartzTask 定时任务
     */
    public void updateJobCron(QuartzTask quartzTask) {
        try {
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            TriggerKey triggerKey = TriggerKey.triggerKey(quartzTask.getJobName(), quartzTask.getJobGroup());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzTask.getCron());
            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            scheduler.rescheduleJob(triggerKey, trigger);
        } catch (SchedulerException e) {
            throw new AosException(ReturnCodes.HTTP_SYSTEM_ERROR, "更新定时任务时间表达式失败");
        }
    }

    /**
     * 补全数据
     *
     * @param originList 原始数据
     * @param startNum   起始数
     * @return 补全数据列表
     */
    private List<Map<String, Object>> completeResult(List<QuartzTask> originList, int startNum) {
        List<Map<String, Object>> resultList = new ArrayList<>();
        if (ObjectUtil.isEmpty(originList)) {
            return resultList;
        }
        int sort = startNum + 1;
        for (QuartzTask item : originList) {
            Map<String, Object> map = ObjectUtils.object2Map(item);
            map.put("sort", sort);
            map.put("statusName", TaskStatusEnum.getNameByValue(item.getStatus()));
            resultList.add(map);
            sort++;
        }
        return resultList;
    }

}